<?php
/**
 * Created by PhpStorm
 * User: whh
 * Date: 2022/12/6
 * Time: 10:08
 */

namespace app\common\command;

use app\common\coroutinePool\MysqlPoolCoroutine;
use OSS\OssClient;
use Swoole\Coroutine;
use Swoole\Runtime;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\facade\Env;
use function Swoole\Coroutine\run;


class NiuNiuTui extends Command
{
    private $ptid = 2;
    private $token = 'zATFWUQcHe8Kl4q5BZaJZ3ndhdhhz4tIqKCr';
    private $novelListUrl = "http://www.niuniutui.com/index/productApi/bookList";
    private $novelInfoUrl = "http://www.niuniutui.com/index/ProductApi/bookInfo";
    private $novelChapterListUrl = "http://www.niuniutui.com/index/ProductApi/chapters";
    private $novelChapterContentUrl = "http://www.niuniutui.com/index/ProductApi/content";


    protected function configure()
    {
        $this->setName("nnt")
            ->addArgument('action', Argument::OPTIONAL, "方法名称")
            ->addOption('offset', null, Option::VALUE_REQUIRED, '采集启始下标')
            ->addOption('length', null, Option::VALUE_REQUIRED, '采集分页长度')
            ->setDescription('采集据点牛牛推3.0书籍');
    }

    protected function execute(Input $input, Output $output)
    {
        $action = $input->getArgument('action');

        if (method_exists($this, $action)) {
            if (strtolower($action) == strtolower('collectNovel')) {
                $offset = 0;
                $length = 0;
                if ($input->hasOption('offset')) {
                    $offset = $input->getOption('offset');
                }
                if ($input->hasOption('length')) {
                    $length = $input->getOption('length');
                }
                $this->collectNovel(MysqlPoolCoroutine::getInstance()->get(), $offset, $length);
            }else{
                $this->$action();
            }
        } else {
            /** @var TYPE_NAME $output */
            die($output->writeln("not method: " . __CLASS__ . "->" . $action));
        }
        $output->writeln(__CLASS__ . " console end;");
    }

    /**
     * 测试协程
     * @Author: whh
     */
    public function test()
    {
        $s = microtime(true);
//        for ($c = 100; $c--;) {
//             for ($n = 100; $n--;) {
//                    echo $n.PHP_EOL;
//                    sleep(1);
//                }
//        }
        // Swoole\Coroutine\run()见'协程容器'章节
        run(function () {
            // i just want to sleep...
            for ($c = 10; $c--;) {
                //创建一个新的协程，并立即执行。
                Coroutine::create(function () use ($c) {
                    for ($n = 100; $n--;) {
                        echo $n . PHP_EOL;
                        sleep(1);
                    }
                });
            }
        });

        echo 'use ' . (microtime(true) - $s) . ' s' . PHP_EOL;
    }


    /**
     * 使用task分页采集数据
     * @Author: whh
     */
    public function runTask()
    {
        $url = $this->novelListUrl . "?pid={$this->ptid}&token={$this->token}";
        $result = doCurl($url);
        $result = json_decode($result, true);
        if (isset($result['code']) && $result['code'] === 0) {
            $novels = $result['data'];
            $limit = 10;
            $page = 1;

            $data = true;
            while ($data && $page <= 60) {//例子
//              while ($data) {//真实情况
                $min = ($limit * ($page - 1)); //最小端
                $data = array_splice($novels, 0, $limit);
                $page++;
                if ($data) {
                    $fp = stream_socket_client('tcp://127.0.0.1:9508', $errno, $errstr, 1);
                    if (!$fp) {
                        echo "{$errstr} ({$errno})\n";
                    } else {
                        $content = json_encode([
                            'cmd' => '_nnt',
                            'offset' => $min,
                            'length' => $limit,
                        ]);
                        fwrite($fp, $content);
                        $data = fread($fp, 1024);
                        echo $data . PHP_EOL;
                        fclose($fp);die;
                    }
                }
            }
        } else {
            echo "get novel list error" . PHP_EOL;
        }
    }

    /**
     * 采集书籍
     * @param $pdo
     * @param int $offset
     * @param int $length
     * @Author: whh
     */
    public function collectNovel($pdo, $offset = 0, $length = 0)
    {
        $s = microtime(true);
        $url = $this->novelListUrl . "?pid={$this->ptid}&token={$this->token}";
        $result = doCurl($url);
        $result = json_decode($result, true);
        $novels = [];
        if (isset($result['code']) && $result['code'] === 0) {
            $novels = $result['data'];
        } else {
            echo "书籍列表获取异常" . PHP_EOL;
        }

        if ($length > 0) {
            $novels = array_splice($novels, $offset, $length);
        }
        // 此行代码后，变成异步IO
        Runtime::enableCoroutine();
        /**
         * 不能使用协程容器，因为我是用task异步任务起吊的，服务已经有入口了，不能重复生成
         */
        foreach ($novels as $k => $novel) {
            //一本书一个协程
            Coroutine::create(function () use ($novel, $pdo) {
                $cid = Coroutine::getCid();//协程id
                try {
                    $this->runNovel($novel, $pdo);
                } catch (\Throwable $e) {
                    echo $e->getMessage() . PHP_EOL;
                }
                Coroutine::defer(function () {
                });//协程结束
            });
        }
    }

    /**
     * 开始跑小说
     * @param $novel
     * @param $pdo
     * @Author: whh
     */
    private function runNovel($novel, $pdo)
    {
        $sql = "SELECT * FROM `novel_mp_party` WHERE party_code = 'nnt' AND party_novel_id = {$novel['book_id']}";
        $statement = $pdo->prepare($sql);
        $statement->execute();
        $mp = $statement->fetch();
        $statement = null;
        if ($mp) {
            //更新书籍
            $this->updateChapter($mp['novel_id'], $novel, $pdo);
        } else {
            //添加书籍
            $this->addNovel($novel, $pdo);
        }
    }

    /**
     * 添加书籍
     * @param $novel
     * @param $pdo
     * @Author: whh
     */
    private function addNovel($novel, $pdo)
    {
        $result = $this->novelInfo($novel);
        if (isset($result['code']) && $result['code'] === 0) {
            $data = $result['data'];
            $cover = '';
            if ($data['verticpic_txt']) {
                $addTime = date("Ymd", time());
                $path = Env::get('ROOT_PATH') . 'public/' . "uploads" . "/" . $addTime . "/";
                if (!file_exists($path)) {
                    mkdir($path, 0777);
                }
                $filename = md5(rand(1, 99999999)) . $this->getExtension($data['verticpic_txt']);
                $cover = $this->downLoadPic($data['verticpic_txt'], $path, $filename);
            }
            $freq_id = $this->mpFreq($data['channel_id']);
            $class_id = $this->mpClass($data['cat_id']);
            $data['lead'] = trim(str_replace("，", ",", $data['lead']));
            try {
                $pdo->beginTransaction();
                $sql = "INSERT INTO novel ( title, origin_title, cover ,author,brief,freq_id,class_id,lead) VALUES ( '{$data['bookname']}', '{$data['bookname']}','{$cover}','{$data['writer']}','{$data['brief']}',{$freq_id},{$class_id},'{$data['lead']}');";
                $conn = $pdo->prepare($sql);
                $conn->execute();
                $conn = null;// 使用完后立即手动归还连接，避免出现协程死锁

                $id = $pdo->lastInsertId();
                $sql = "INSERT INTO novel_mp_party ( novel_id, party_code, party_novel_id ) VALUES ( {$id}, 'nnt', {$novel['book_id']});";
                $conn = $pdo->prepare($sql);
                $conn->execute();
                $conn = null;// 使用完后立即手动归还连接，避免出现协程死锁
                $pdo->commit();
            } catch (\Exception $e) {
                $pdo->rollBack();
                throw new \RuntimeException("{$novel['book_id']}采集异常：{$e->getMessage()}");
            }
            $this->updateChapter($id, $data, $pdo, 1);
        } else {
            throw new \RuntimeException("{$novel['book_id']}_{$novel['bookname']}：书籍信息获取异常");
        }
    }

    /**
     * 映射频道
     * @param $value
     * @return int
     * @Author: whh
     */
    private function mpFreq($value)
    {
        $freq_id = 0;
        if ($value == 17 || $value == 19) {
            $freq_id = 1;
        } elseif ($value == 18) {
            $freq_id = 2;
        }
        return $freq_id;
    }


    /**
     * 映射分类
     * @param $value
     * @return int
     * @Author: whh
     */
    private function mpClass($value)
    {
        $class_id = 0;
        $classArr = [
            9 => 3,
            10 => 4,
            11 => 5,
            13 => 7,
            14 => 9,
            21 => 1,
            38 => 11,
            2 => 16,
            3 => 17,
            4 => 20,
            5 => 18,
            22 => 18,
            20 => 12,
            40 => 12,
            41 => 12,
            42 => 6,
            43 => 12,
        ];
        return isset($classArr[$value]) ? $classArr[$value] : $class_id;
    }


    /**
     * 小说详情
     * @param $novel
     * @return mixed
     * @Author: whh
     */
    private function novelInfo($novel)
    {
        $url = $this->novelInfoUrl . "?pid={$this->ptid}&token={$this->token}&book_id={$novel['book_id']}";
        $result = doCurl($url);
        $result = json_decode($result, true);
        return $result;
    }


    /**
     * 更新书籍
     * @param $novelId
     * @param $partyNovel
     * @param $pdo
     * @param int $is_add 来源是添加书籍
     * @Author: whh
     */
    private function updateChapter($novelId, $partyNovel, $pdo, $is_add = 0)
    {
        $sql = "select id,update_status from novel where id = {$novelId}";
        $conn = $pdo->prepare($sql);
        $conn->execute();//执行
        $novel = $conn->fetch();
        $conn = null;
        if ($novel && $novel['update_status'] == 0) {
            if ($is_add === 0) {
                $result = $this->novelInfo($partyNovel);
                if (isset($result['code']) && $result['code'] === 0) {
                    $partyNovel = $result['data'];
                } else {
                    throw new \RuntimeException("{$partyNovel['book_id']}_{$partyNovel['bookname']}：书籍信息获取异常");
                }
            }
            /**
             * 更新吧
             */
            $sql = "SELECT max(chapter_no) chapter_no  from novel_chapter where novel_id = ?";
            $conn = $pdo->prepare($sql);
            $conn->execute([$novelId]);//执行
            $maxR = $conn->fetch();
            $conn = null;
            $maxChapterNo = $maxR['chapter_no'];
            $result = $this->chapterList($partyNovel['book_id']);
            if (isset($result['code']) && $result['code'] === 0) {
                $chapterList = $result['data'];
                $chapter_no = 1;
                $chapterCount = count($chapterList);
                foreach ($chapterList as $k => $v) {
                    //续采
                    if (($k + 1) > $maxChapterNo) {
                        $contentResult = $this->chapterContent($partyNovel['book_id'], $chapter_no);
                        if (isset($contentResult['code']) && $contentResult['code'] === 0) {
                            $content = $contentResult['data']['content'];
                            //内容
                            $words = clearHtmlGetNum($content);
                            try {
                                $pdo->beginTransaction();
                                $sql = "INSERT INTO novel_chapter (`name`, novel_id, chapter_no ,words) VALUES (?,?,?,?);";
                                $conn = $pdo->prepare($sql);
                                $conn->execute(["{$v['title']}", $novelId, $chapter_no, $words]);
                                $conn = null;

                                $fb_table = "novel_chapter_content_" . intval(substr($novelId, -2));
                                $sql = "INSERT INTO {$fb_table} (novel_id, chapter_no ,content) VALUES (?,?,?);";
                                $conn = $pdo->prepare($sql);
                                $conn->execute([$novelId, $chapter_no, $content]);
                                $conn = null;

                                //更新书籍章节字数
                                $sql = "update novel set number_words = number_words + ? where id = ?;";
                                $conn = $pdo->prepare($sql);
                                $conn->execute([$words, $novelId]);
                                $conn = null;

                                //章节更新时间
                                $sql = "update novel set chapter_up_time = ? where id = ?;";
                                $conn = $pdo->prepare($sql);
                                $conn->execute([date("Y-m-d H:i:s"), $novelId]);
                                $conn = null;

                                $pdo->commit();
                            } catch (\Exception $e) {
                                $pdo->rollBack();
                                throw new \RuntimeException("{$partyNovel['book_id']}_{$partyNovel['bookname']}_{$chapter_no}：{$e->getMessage()}");
                            }
                        } else {
                            throw new \RuntimeException("{$partyNovel['book_id']}_{$partyNovel['bookname']}_{$chapter_no}：章节获取内容异常");
                        }
                    }
                    $chapter_no++;
                    /**
                     * 如果是最后一章，更改连载状态
                     */
                    if ($chapterCount == ($k + 1)) {
                        if ($partyNovel['update_status'] == 1) {
                            $sql = "update novel set update_status = 1 where id = ?;";
                            $conn = $pdo->prepare($sql);
                            $conn->execute([$novelId]);
                            $conn = null;
                        }
                    }
                }
            } else {
                throw new \RuntimeException("{$partyNovel['book_id']}_{$partyNovel['bookname']}：章节列表获取异常");
            }
        }
    }


    /**
     * 章节
     * @param $bookID
     * @return mixed
     * @Author: whh
     */
    private function chapterList($bookID)
    {
        $url = $this->novelChapterListUrl . "?pid={$this->ptid}&token={$this->token}&book_id={$bookID}";
        $result = doCurl($url);
        $result = json_decode($result, true);
        return $result;
    }


    /**
     * 章节内容
     * @param $bookID
     * @return mixed
     * @Author: whh
     */
    private function chapterContent($bookID, $chapnum)
    {
        $url = $this->novelChapterContentUrl . "?pid={$this->ptid}&token={$this->token}&book_id={$bookID}&chapnum={$chapnum}";
        $result = doCurl($url);
        $result = json_decode($result, true);
        return $result;
    }


    /**
     * 下载图片
     * @param $picUrl
     * @param $path
     * @param $filename
     * @return string
     * @Author: whh
     */
    private function downLoadPic($picUrl, $path, $filename)
    {
        $curl = curl_init();
        $fp = fopen($path . $filename, 'w');
        curl_setopt($curl, CURLOPT_URL, $picUrl);
        curl_setopt($curl, CURLOPT_FILE, $fp);
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
        curl_setopt($curl, CURLOPT_TIMEOUT, 30);
        curl_setopt($curl, CURLOPT_ENCODING, 'gzip, deflate');
        curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)');
        $headers [] = 'Accept: textml,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
        $headers [] = 'Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3';
        $headers [] = 'Connection: keep-alive';
        $headers [] = 'Cache-Control: max-age=0';
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        $res = curl_exec($curl);
        curl_close($curl);
        fclose($fp);
        if ($res) {
            $filepath = $path . $filename;
            $size = filesize($filepath);
            if ($size > 512000) {
                $filepath = $this->compress($filepath);//图片大于500kb就压缩
            }
            $ossPath = $this->putFileToOss($filename, $filepath);
            return $ossPath;
        } else {
            return '';
        }
    }

    /**
     * 上传oss
     * @param $filename
     * @param $filepath
     * @return string
     * @throws \OSS\Core\OssException
     * @Author: whh
     */
    private function putFileToOss($filename, $filepath)
    {
        $dirName = 'quick_cms/novel';
        $filename = $dirName . '/' . date('Ymd') . '/' . $filename;
        $kv['AccessKeyID'] = config('oss_accessKeyID');
        $kv['AccessKeySecret'] = config('oss_accessKeySecret');
        $kv['OSS_ENDPOINT'] = config('oss_endPoint');
        $kv['OSS_BUCKET'] = config('oss_bucket');
        $ossClient = new OssClient($kv['AccessKeyID'], $kv['AccessKeySecret'], $kv['OSS_ENDPOINT']);
        $rs = $ossClient->uploadFile($kv['OSS_BUCKET'], $filename, $filepath);
        if (isset($rs['oss-request-url'])) {
            $arr = explode($dirName, $rs['oss-request-url']);
            $rs['shortpath'] = $arr[1];
        }
        @unlink($filepath);
        return "/" . $dirName . $rs['shortpath'];
    }


    /**
     * 获取后缀
     * @param $file
     * @return string
     * @Author: whh
     */
    private function getExtension($file)
    {
        $info = pathinfo($file);
        $extension = 'jpg';
        if (isset($info['extension']) && $info['extension'] != '') {
            $extension = $info['extension'];
        }
        return '.' . $extension;
    }


    /**
     * 压缩图片
     * @param $filename
     * @return string
     * @Author: whh
     */
    public function compress($filename)
    {
        // 源文件
        $percent = 1;
        // Content type
        header('Content-Type: image/jpeg');
        // Get new dimensions
        list($width, $height) = getimagesize($filename);
        $new_width = $width * $percent;
        $new_height = $height * $percent;
        // Resample
        $image_p = imagecreatetruecolor($new_width, $new_height);
        $suffix = $this->getExtension($filename);
        if (strpos($suffix, 'png')) {
            $image = imagecreatefrompng($filename);
        } else {
            $image = imagecreatefromjpeg($filename);
        }
        imagecopyresampled($image_p, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
        // Output
        imagedestroy($image);
        $path = Env::get('ROOT_PATH') . 'public/' . "uploads" . "/" . date("Ymd", time()) . "_compress" . "/";
        if (!file_exists($path)) {
            mkdir($path, 0777);
        }
        $image_p_path = md5(rand(1, 99999999)) . $suffix;
        imagejpeg($image_p, $image_p_path);
        @unlink($filename);
        return $image_p_path;
    }

}